#!/usr/bin/python

import weakref

from base import *
import weakmeth

class Dict_WeakKeyIfPossible: # does not support full dictionary API
	
	def __init__(me):
		me.strong = {}
		me.weak   = weakref.WeakKeyDictionary()
		
	def has_key(me,key):
		return me.weak.has_key(key) or me.strong.has_key(key)
	
	def __getitem__(me,key):
		try:
			return me.weak[key]
		except KeyError:
			return me.strong[key]
		except TypeError:
			return me.strong[key]
		
	def __setitem__(me,key,val):
		try:
			me.weak[key]=val
		except TypeError:
			me.strong[key]=val
			
	def items(me):
		return me.weak.items() + me.strong.items()
		
class EventBus:
	ALL = -1
	
	def __init__(me):
		me.broadcasters    = Dict_WeakKeyIfPossible()
		
		me.event_stack     = []
		
		me.event_mask      = None
		me.event_source    = None
		me.event_data      = None
	
	def remove_dead_listeners(me,listeners):
		i=0
		while i < len(listeners):
			if listeners[i].callback_func.is_dead():
				#print 'removing',i
				del listeners[i]
			else:
				i+=1
				
	def AddBroadcaster(me, br):
		assert not me.broadcasters.has_key(br), "can't be in here twice"

		me.broadcasters[br]=[]
		
	def Broadcast(me, broadcaster, mask=ALL, data=None):
		
		me.event_stack.append((me.event_stack, me.event_source, me.event_data))
		
		me.event_mask      = mask
		me.event_source    = broadcaster
		me.event_data      = data
		
		try:
			#print 'EventBus.broadcast:',broadcaster,mask
			assert mask<>0,'zero mask makes no sense for broadcasting something'
			
			assert me.broadcasters.has_key(broadcaster), (broadcaster,"unknown broadcaster, must first register using .AddBroadcaster()")
			
			list = me.broadcasters[broadcaster]
									
			me.remove_dead_listeners(list)
			
			for listener in list:
				if not mask & listener.mask: continue
				listener.callback_func()
				
		finally:
			del me.event_source # don't want to keep it alive
			
			me.event_mask, me.event_source, me.event_data = me.event_stack.pop()
			
	def assert_inside_event(me):
		assert me.event_mask, "may only call this while event is being handled"

	def GetEventMask(me):
		me.assert_inside_event()
		return me.event_mask
				
	def GetEventSource(me):
		me.assert_inside_event()
		return me.event_source
				
	def GetEventData(me):
		me.assert_inside_event()
		return me.event_data
		
	def GetEventSourceMaskData(me):
		me.assert_inside_event()
		return me.event_source, me.event_mask, me.event_data
						
	def ListenFor(me,broadcaster,callback_func,mask=ALL):
		listeners = me.broadcasters[broadcaster]
		
		callback_func = weakmeth.weak_method_wrap(callback_func)
		#print callback_func
		
		for lr in listeners:
			assert callback_func <> lr.callback_func, ("can't register the same callback twice",callback_func.weakref(),lr.callback_func.weakref())
		
		listeners.append( struct(callback_func=callback_func, mask=mask) )
		
def sanity_test():
	"""
	>>> import gc,eventbus
	>>> bus=eventbus.EventBus()
	
	Create a broadcaster
	>>> class Br:
	...     def __init__(me):
	...             bus.AddBroadcaster(me)
	...     def Broadcast(me,mask=-1,data=None):
	...             me.mask=mask
	...             bus.Broadcast(me,me.mask,data)
	... 
	>>> br=Br()
	
	Register some listeners
	>>> def h1():print 'h1'
	... 
	>>> def hall():print 'hall'
	... 
	>>> bus.ListenFor(br,hall)
	>>> bus.ListenFor(br,h1,0x1)

	Fire off some messages
	>>> br.Broadcast()
	hall
	h1
	>>> br.Broadcast(0x2)
	hall
	
	Dead listeners removed?
	>>> del h1
	>>> gc.collect()
	0
	>>> br.Broadcast()
	hall
	
	Dead broadcasters removed?
	>>> del br
	>>> gc.collect()
	0
	>>> print bus.broadcasters.items()
	[]
	
	Check bus.GetEventSource().name, bus.GetEventMask(), bus.GetEventData()
	>>> def hprint(): print bus.GetEventSource().name, bus.GetEventMask(), bus.GetEventData()
	>>> br1=Br()
	>>> br1.name='br1'
	>>> br2=Br()
	>>> br2.name='br2'
	>>> bus.ListenFor(br1,hprint)
	>>> bus.ListenFor(br2,hprint)
	>>> br1.Broadcast()
	br1 -1 None
	>>> br2.Broadcast(0x5,data='foobar')
	br2 5 foobar
	
	Check error checking
	>>> bus.GetEventSource()
	Traceback (most recent call last):
	AssertionError: may only call this while event is being handled
	>>> bus.GetEventMask()()
	Traceback (most recent call last):
	AssertionError: may only call this while event is being handled
	>>> bus.GetEventData()
	Traceback (most recent call last):
	AssertionError: may only call this while event is being handled
	
	Test a string broadcaster
	>>> bus.AddBroadcaster('reset-line-mod-status-hook')
	
	Check that we can't add a broadcaster twice
	>>> br=Br()
	>>> bus.AddBroadcaster(br)
	Traceback (most recent call last):
	AssertionError: can't be in here twice
	
	Check we can't add a handler twice
	>>> bus.ListenFor(br,hprint)
	>>> bus.ListenFor(br,hprint) #doctest: +IGNORE_EXCEPTION_DETAIL
	Traceback (most recent call last):
	AssertionError: can't register the same callback twice
	
	Check we informatively complain if try to broadcast from non-existent broadcaster
	>>> bus.Broadcast('foo')
	Traceback (most recent call last):
	AssertionError: ('foo', 'unknown broadcaster, must first register using .AddBroadcaster()')
	
	"""
	
def test_recursive_broadcasts():
	"""
	>>> bus = EventBus()
	>>> bus.AddBroadcaster(1)
	>>> bus.AddBroadcaster(2)
	>>> def h1():
	... 	print "h1 beg"
	...	bus.Broadcast(2)
	... 	print "h1 end"
	...
	>>> bus.ListenFor(1,h1)
	>>> bus.Broadcast(1)
	h1 beg
	h1 end
	>>> def h2():print 'h2'
	>>> bus.ListenFor(2,h2)
	>>> bus.Broadcast(2)
	h2
	
	Now the recursive case
	>>> bus.Broadcast(1)
	h1 beg
	h2
	h1 end
	"""
				
if __name__=="__main__":
	import doctest
	doctest.testmod()

